#include <cstdlib>

#include <unistd.h>

#include <string>
#include <iostream>
#include <fstream>

#include "mtf"
#include "bs"
#include "RLE"

using namespace std;

int compress(istream *inFile, ostream *outFile, integer blocksize);
int decompress(istream *inFile, ostream *outFile, integer blocksize);

const integer def_blocksize = 1024*1024;
const integer RLE_TRESHOLD = 1;

int main(int argc, char **argv) {
	int arg = 0;
	integer blocksize = def_blocksize;
	bool
		C = 0, c = 0,
		X = 0, x = 0,
		O = 0, o = 0,
		error = 0;
	istream *inFile = nullptr;
	ostream *outFile = nullptr;
	string
		inFilename,
		outFilename,
		newInFilename,
		newOutFilename;
	while((arg = getopt(argc, argv, "Cc:Xx:Oo:")) != -1) {
		switch(arg) {
			case 'C':
				C = 1;
				inFile = &cin;
				outFile = &cout;
				break;
			case 'c':
				c = 1;
				inFilename = optarg;
				break;
			case 'X':
				X = 1;
				inFile = &cin;
				outFile = &cout;
				break;
			case 'x':
				x = 1;
				inFilename = optarg;
				break;
			case 'O':
				O = 1;
				outFile = &cout;
				break;
			case 'o':
				o = 1;
				outFilename = optarg;
				break;
			default:
				error = 1;
		}
	}

	if(((c + C + x + X) != 1) or (o and O) or error) {
		cerr << "HELP" << endl;
		exit(EXIT_FAILURE);
	}

	if(c and !(o or O)) {
		newOutFilename = inFilename + ".XX";
		outFilename = newOutFilename; 
	}

	if(x) {
		if(inFilename.compare(inFilename.length() - 3, 3, ".XX")) {
			newInFilename = inFilename + ".XX";
			if(!(o or O)) outFilename = inFilename;
			inFilename = newInFilename;
		} else if(!(o or O)) {
			newOutFilename = inFilename.substr(0, inFilename.length() - 3);
			outFilename = newOutFilename;
		}
	}

	cerr << "in:\t" << inFilename << endl << "out:\t" << outFilename << endl <<
		"C:" << C << "\tc:" << c << endl <<
		"X:" << X << "\tx:" << x << endl <<
		"O:" << O << "\to:" << o << endl;

	if(c or o or x) {
		if(c or x) {
			inFile = new ifstream(inFilename);
		}
		if(!*inFile) {
			cerr << "Bad input file: " << inFilename << endl;
			error = 1;
		} else if(!O) {
			outFile = new ofstream(outFilename);
			if((!outFile) or (!*outFile)) {
				cerr << "Bad output file: " << outFilename << endl;
				error = 1;
			}
		}
	}

	if(!error) ((c or C)?compress:decompress)(inFile, outFile, blocksize);

	if((inFile != &cin) and inFile) delete inFile;
	if((outFile != &cout) and outFile) delete outFile;
	exit(error? EXIT_FAILURE: EXIT_SUCCESS);
}

int compress(istream *inFile, ostream *outFile, integer blocksize) {
	integer s = blocksize;
	integer rle_size = 0;
	integer i = 0;
	uchar block[blocksize],
		  rle_block[3*blocksize/2];
	mxf * the_mtf = new mtf();
	while(inFile->good() and (s == blocksize)) {
		inFile->read((char*)block, blocksize); s = inFile->gcount();
		i = bs(block, s);
std::cerr << "I:" << i;
std::cerr << "\tS:" << s << std::endl;
		(*the_mtf)(block, s);
		outFile->write((char*)&i, sizeof(i));
//		outFile->write((char*)block, s);
// RLE selectivo
//		for(integer j = 0; j < s; j++) {
//			uchar c;
//			outFile->put((c = block[j]));
//			if(c < RLE_TRESHOLD) {
//				uchar n = 0;
//				while((j + n + 1 < s)
//								&& (block[j + n + 1] == c)
//								&& (n < UCHAR_MAX)) {
//					n++;
//				}
//				j += n;
//				outFile->put(n);
//			}
//		}

		rle_size = rle(block,rle_block,s);
		outFile->write((char*)&rle_size, sizeof(rle_size));
		outFile->write((char*)rle_block, rle_size);

	}
	delete the_mtf;
	return 0;
}

int decompress(istream *inFile, ostream *outFile, integer blocksize) {
	integer s = blocksize;
	integer rle_size = 3*blocksize/2;
	integer i = 0;
	uchar block[blocksize],
		  rle_block[3*blocksize/2];
	mxf * the_mff = new mff();
	while(inFile->good() and (s == blocksize)) {
		inFile->read((char*)&i, sizeof(i));
std::cerr << "I:" << i;
//		inFile->read((char*)block, s); s = inFile->gcount();
// RLE selectivo
//		for(s = 0; s < blocksize; s++) {
//			int c = inFile->get();
//			if(c == EOF) break;
//			if((block[s] = c) < RLE_TRESHOLD) {
//				uchar n = inFile->get();
//				while(n--) block[++s] = c;
//			}
//		}
		inFile->read((char*)&rle_size, sizeof(rle_size));
		inFile->read((char*)rle_block, rle_size);
		rle_size = inFile->gcount();  // me parece que no hace falta el s=... siempre es s==i?

		s = derle(rle_block,block,rle_size);
std::cerr << "\tS:" << s << std::endl;
		(*the_mff)(block, s);
		debs(block, i, s);
		outFile->write((char*)block, s);
	}
	delete the_mff;
	return 0;
}

